home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / userprocs < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  49.7 KB  |  1,458 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) userprocs.gawk 2.0 97/05/21
  4. # 94/01/20 john h. dubois iii (john@armory.com)
  5. # 94/02/11 Added all options.
  6. # 95/05/03 Added a and n options.
  7. # 96/05/09 2.0  Largely rewritten.  Made <minprocs> be given with -M.
  8. #          Let users be named on command line.
  9. # 96/10/09 Added r option.
  10. # 97/05/21 Added s option.
  11. # 97/05/31 Added xH options.
  12.  
  13. BEGIN {
  14.     Name = "userprocs"
  15.     Usage = \
  16.     "Usage:\n"\
  17. Name " [-ahHrs] [-n<num>] [-t<topusers>] [-c<cols>] [-M<minprocs>] [user ...]"
  18.     ARGC = Opts(Name,Usage,"ahHrt<c<n>M>sx",0)
  19.     if ("h" in Options) {
  20.     printf \
  21. "%s: show how many processes each user is running.\n"\
  22. "%s \n"\
  23. "One line is printed for each user who is running any processes, telling\n"\
  24. "how many processes the user is running along with a summary of what they\n"\
  25. "are.  Output is sorted by the total number of processes each user is\n"\
  26. "running.  The output is truncated to COLUMNS-1 columns, and only the top\n"\
  27. "LINES-2 lines are printed, where COLUMNS and LINES are taken from the\n"\
  28. "environment if set.  If not, the values for the terminal type in use are\n"\
  29. "used; if it has none, they default to 80 and 24 respectively.\n"\
  30. "Options:\n"\
  31. "-h: Print this help.\n"\
  32. "-H: Print a header.\n"\
  33. "-a: Instead of a per-user report, print a single report for all processes,\n"\
  34. "    listing the number of instances of each process.  The output line is\n"\
  35. "    not truncated.  This option also makes <num> as described for the -n\n"\
  36. "    option default to two.\n"\
  37. "-M<minprocs>: Report only on those users who are running at least\n"\
  38. "    <minprocs> processes.  If all users running at least <minprocs>\n"\
  39. "    processes should be reported on, regardless of how many such users\n"\
  40. "    there are, -t0 should also be given.\n"\
  41. "-n<num>: For each user, report only on processes that the user is running\n"\
  42. "    at least <num> times.  If -a is given, report only on processes that\n"\
  43. "    are running at least <num> times total.\n"\
  44. "-r: Generate report based on real user IDs rather than effective user IDs.\n"\
  45. "-s: Report only on users who have a shell listed in /etc/shells.\n"\
  46. "-t<top>: Show information for the top <top> users running the most\n"\
  47. "    processes.  If 0 is given, information is shown for all users running\n"\
  48. "    processes.\n"\
  49. "-c<columns>: Truncate the display to <columns> columns.  If 0 is given,\n"\
  50. "    the display is not truncated.\n",Name,Usage,minUID
  51.     exit(0)
  52.     }
  53.  
  54.     Debug = "x" in Options
  55.     if ((chkShell = ("s" in Options)) && !makeShellUser(ShellUser)) {
  56.     print "Error reading /etc/shells or /etc/passwd" > "/dev/stderr"
  57.     exit 1
  58.     }
  59.     Lines = Cols = 0
  60.     LineGap = 2
  61.     ColGap = 1
  62.     if ("a" in Options) {
  63.     AllRept = 1
  64.     Lines = -1
  65.     Cols = -1
  66.     minProcInstances = 2
  67.     }
  68.     else
  69.     minProcInstances = 1
  70.     if ("n" in Options)
  71.     minProcInstances = Options["n"]
  72.  
  73.     if ("t" in Options) {
  74.     Lines = Options["t"]
  75.     LineGap = 0
  76.     }
  77.     if ("c" in Options) {
  78.     Cols = Options["c"]
  79.     ColGap = 0
  80.     if (!Cols)
  81.         Cols = -1
  82.     }
  83.     HeadTailInit(Lines,Cols,LineGap,ColGap)
  84.  
  85.     if ("M" in Options)
  86.     minUserProcs = Options["M"]
  87.     if (ARGC > 1)
  88.     for (i = 1; i < ARGC; i++)
  89.         Users[ARGV[i]]
  90.  
  91.     # Gather process information
  92.     if ((nprocs = getPS(PIDs,Data,
  93.     ("r" in Options ? "RUSER" : "UID") ",COMM",Children,Debug,"")) < 0) {
  94.     printf "%s: ps failed.\n",Name > "/dev/stderr"
  95.     exit 1
  96.     }
  97.     delete PIDs["ps"]
  98.  
  99.     countProcs(PIDs,Data,AllRept,Users,chkShell,NProc,Procs)
  100.  
  101.     Format = "%-8s %3s  %s"
  102.     if ("H" in Options)
  103.     printf Format "\n","User","#","Processes"
  104.     nus = Report(NProc,Procs,minUserProcs,minProcInstances,Format)
  105.     if (!AllRept)
  106.     # Print values before descriptions, so it will be obvious if
  107.     # column truncation is occuring.
  108.     ColPrint(\
  109.     sprintf("%d user(s) reported on;  %d total process(es) on system.",
  110.     nus,nprocs))
  111. }
  112.  
  113. # AllRept: true if a summary for all users should be printed instead of a
  114. # per-user report.
  115. # Globals used: ShellUser[]
  116. # Output vars:
  117. # NProc[u] is set to the number of processes user u is running.
  118. # Procs[u] is a string consisting of the process accounting name of all of the
  119. # processes user u is running, separated by whitespace.
  120. function countProcs(PIDs,Data,AllRept,Users,chkShell,NProc,Procs,
  121. pid,u,uListGiven) {
  122.     uListGiven = !IsEmpty(Users)
  123.     # Count processes by user
  124.     if (AllRept)
  125.     u = "All"
  126.     for (pid in PIDs) {
  127.     if (!AllRept || uListGiven)
  128.         u = Data[pid,"UID"]
  129.     # Skip processes we are not interested in
  130.     if (uListGiven && !(u in Users) || chkShell && !(ShellUser[u]))
  131.         continue
  132.     if (!((pid,"COMM") in Data)) {
  133.         printf "Error: pid %d not found in Data[]\n",pid > "/dev/stderr"
  134.         continue
  135.     }
  136.     NProc[u]++
  137.     Procs[u] = Procs[u] " " Data[pid,"COMM"]
  138.     }
  139. }
  140.  
  141. function Report(NProc,Procs,minUserProcs,minProcInstances,Format,
  142. nRep,u,nus,i,k) {
  143.     nus = qsortArbIndByValue(NProc,k)
  144.     for (i = nus; i; i--) {
  145.     u = k[i]
  146.     if (minUserProcs && NProc[u] < minUserProcs+0)
  147.         break
  148.     PList = MakeProcList(Procs[u],minProcInstances)
  149.     if (PList != "") {
  150.         nRep++
  151.         if (HeadPrint(sprintf(Format,u,NProc[u],PList)) != 1)
  152.         break
  153.     }
  154.     }
  155.     return nRep
  156. }
  157.  
  158. # Sort processes by the number of times they are being run.
  159. # Build a process count list.  Skip those being run less then Min times.
  160. function MakeProcList(PList,Min,  Arr,NProcs,ProcCount,Num,Proc,Out,i,k) {
  161.     NProcs = split(PList,Arr)
  162.     for (i = 1; i <= NProcs; i++)
  163.     ProcCount[Arr[i]]++
  164.     qsortArbIndByValue(ProcCount,k)
  165.     for (i = NProcs; i > 0; i--) {
  166.     Proc = k[i]
  167.     if (Proc == "")
  168.         continue
  169.     Num = ProcCount[Proc]
  170.     if (Num < Min)
  171.         break
  172.     if (Out == "")
  173.         Out = Proc
  174.     else
  175.         Out = Out " " Proc
  176.     if (Num > 1)
  177.         Out = Out "*" Num
  178.     }
  179.     return Out
  180. }
  181. ### end of main program
  182.  
  183. ### Start of ProcArgs library
  184. # @(#) ProcArgs 1.11 96/12/08
  185. # 92/02/29 john h. dubois iii (john@armory.com)
  186. # 93/07/18 Added "#" arg type
  187. # 93/09/26 Do not count -h against MinArgs
  188. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  189. #          Removed meaning of "+" or "-" by itself.
  190. # 94/03/08 Added & option and *()< option types.
  191. # 94/04/02 Added NoRCopt to Opts()
  192. # 94/06/11 Mark numeric variables as such.
  193. # 94/07/08 Opts(): Do not require any args if h option is given.
  194. # 95/01/22 Record options given more than once.  Record option num in argv.
  195. # 95/06/08 Added ExclusiveOptions().
  196. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  197. #          Expand $VARNAME at the start of its filenames.
  198. #          Let varname=0 and -option- turn off an option.
  199. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  200. #          of the vars should be searched for in the environment.
  201. #          Check for duplicate rcfiles.
  202. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  203. #          now return various negatives values on error, not just -1, and
  204. #          Opts() may set Err to various positive values, not just 1.
  205. #          Added AllowUnrecOpt.
  206. # 96/05/23 Check type given for & option
  207. # 96/06/15 Re-port to awk
  208. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  209. #          used by other functions.
  210. # 96/10/15 Added OptChars
  211. # 96/11/01 Added exOpts arg to Opts()
  212. # 96/11/16 Added ; type
  213. # 96/12/08 Added Opt2Set() & Opt2Sets()
  214. # 96/12/27 Added CmdLineOpt()
  215.  
  216. # optlist is a string which contains all of the possible command line options.
  217. # A character followed by certain characters indicates that the option takes
  218. # an argument, with type as follows:
  219. # :    String argument
  220. # ;    Non-empty string argument
  221. # *    Floating point argument
  222. # (    Non-negative floating point argument
  223. # )    Positive floating point argument
  224. # #    Integer argument
  225. # <    Non-negative integer argument
  226. # >    Positive integer argument
  227. # The only difference the type of argument makes is in the runtime argument
  228. # error checking that is done.
  229.  
  230. # The & option is a special case used to get numeric options without the
  231. # user having to give an option character.  It is shorthand for [-+.0-9].
  232. # If & is included in optlist and an option string that begins with one of
  233. # these characters is seen, the value given to "&" will include the first
  234. # char of the option.  & must be followed by a type character other than ":"
  235. # or ";".
  236. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  237.  
  238. # Strings in argv[] which begin with "-" or "+" are taken to be
  239. # strings of options, except that a string which consists solely of "-"
  240. # or "+" is taken to be a non-option string; like other non-option strings,
  241. # it stops the scanning of argv and is left in argv[].
  242. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  243. # If an option takes an argument, the argument may either immediately
  244. # follow it or be given separately.
  245. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  246. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  247. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  248. # this feature had a flaw that caused problems in some cases.  See the OptChars
  249. # parameter to explicitly set the option-specifier characters.
  250.  
  251. # If an option that does not take an argument is given,
  252. # an index with its name is created in Options and its value is set to the
  253. # number of times it occurs in argv[].
  254.  
  255. # If an option that does take an argument is given, an index with its name is
  256. # created in Options and its value is set to the value of the argument given
  257. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  258. # If an option that takes an argument is given more than once,
  259. # Options[option-name,"count"] is incremented, and the value is assigned to
  260. # the index (option-name,instance) where instance is 2 for the second occurance
  261. # of the option, etc.
  262. # In other words, the first time an option with a value is encountered, the
  263. # value is assigned to an index consisting only of its name; for any further
  264. # occurances of the option, the value index has an extra (count) dimension.
  265.  
  266. # The sequence number for each option found in argv[] is stored in
  267. # Options[option-name,"num",instance], where instance is 1 for the first
  268. # occurance of the option, etc.  The sequence number starts at 1 and is
  269. # incremented for each option, both those that have a value and those that
  270. # do not.  Options set from a config file have a value of 0 assigned to this.
  271.  
  272. # Options and their arguments are deleted from argv.
  273. # Note that this means that there may be gaps left in the indices of argv[].
  274. # If compress is nonzero, argv[] is packed by moving its elements so that
  275. # they have contiguous integer indices starting with 0.
  276. # Option processing will stop with the first unrecognized option, just as
  277. # though -- was given except that unlike -- the unrecognized option will not be
  278. # removed from ARGV[].  Normally, an error value is returned in this case.
  279. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  280. # be found, so the number of remaining arguments is returned instead.
  281. # If OptChars is not a null string, it is the set of characters that indicate
  282. # that an argument is an option string if the string begins with one of the
  283. # characters.  A string consisting solely of two of the same option-indicator
  284. # characters stops the scanning of argv[].  The default is "-+".
  285. # argv[0] is not examined.
  286. # The number of arguments left in argc is returned.
  287. # If an error occurs, the global string OptErr is set to an error message
  288. # and a negative value is returned.
  289. # Current error values:
  290. # -1: option that required an argument did not get it.
  291. # -2: argument of incorrect type supplied for an option.
  292. # -3: unrecognized (invalid) option.
  293. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  294. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  295. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  296. {
  297. # ArgNum is the index of the argument being processed.
  298. # ArgsLeft is the number of arguments left in argv.
  299. # Arg is the argument being processed.
  300. # ArgLen is the length of the argument being processed.
  301. # ArgInd is the position of the character in Arg being processed.
  302. # Option is the character in Arg being processed.
  303. # Pos is the position in OptList of the option being processed.
  304. # NumOpt is true if a numeric option may be given.
  305.     ArgsLeft = argc
  306.     NumOpt = index(OptList,"&")
  307.     OptionNum = 0
  308.     if (OptChars == "")
  309.     OptChars = "-+"
  310.     while (OptChars != "") {
  311.     c = substr(OptChars,1,1)
  312.     OptChars = substr(OptChars,2)
  313.     OptCharSet[c]
  314.     OptTerm[c c]
  315.     }
  316.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  317.     Arg = argv[ArgNum]
  318.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  319.         break    # Not an option; quit
  320.     if (Arg in OptTerm) {
  321.         delete argv[ArgNum]
  322.         ArgsLeft--
  323.         break
  324.     }
  325.     ArgLen = length(Arg)
  326.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  327.         Option = substr(Arg,ArgInd,1)
  328.         if (NumOpt && Option ~ /[-+.0-9]/) {
  329.         # If this option is a numeric option, make its flag be & and
  330.         # its option string flag position be the position of & in
  331.         # the option string.
  332.         Option = "&"
  333.         Pos = NumOpt
  334.         # Prefix Arg with a char so that ArgInd will point to the
  335.         # first char of the numeric option.
  336.         Arg = "&" Arg
  337.         ArgLen++
  338.         }
  339.         # Find position of flag in option string, to get its type (if any).
  340.         # Disallow & as literal flag.
  341.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  342.         if (AllowUnrecOpt) {
  343.             Escape = 1
  344.             break
  345.         }
  346.         else {
  347.             OptErr = "Invalid option: " specGiven Option
  348.             return -3
  349.         }
  350.         }
  351.  
  352.         # Find what the value of the option will be if it takes one.
  353.         # NeedNextOpt is true if the option specifier is the last char of
  354.         # this arg, which means that if the option requires a value it is
  355.         # the next arg.
  356.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  357.         if (GotValue = ArgNum + 1 < argc)
  358.             Value = argv[ArgNum+1]
  359.         }
  360.         else {    # Value is included with option
  361.         Value = substr(Arg,ArgInd + 1)
  362.         GotValue = 1
  363.         }
  364.  
  365.         if (HadValue = AssignVal(Option,Value,Options,
  366.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  367.         specGiven)) {
  368.         if (HadValue < 0)    # error occured
  369.             return HadValue
  370.         if (HadValue == 2)
  371.             ArgInd++    # Account for the single-char value we used.
  372.         else {
  373.             if (NeedNextOpt) {    # option took next arg as value
  374.             delete argv[++ArgNum]
  375.             ArgsLeft--
  376.             }
  377.             break    # This option has been used up
  378.         }
  379.         }
  380.     }
  381.     if (Escape)
  382.         break
  383.     # Do not delete arg until after processing of it, so that if it is not
  384.     # recognized it can be left in ARGV[].
  385.     delete argv[ArgNum]
  386.     ArgsLeft--
  387.     }
  388.     if (compress != 0) {
  389.     dest = 1
  390.     src = argc - ArgsLeft + 1
  391.     for (count = ArgsLeft - 1; count; count--) {
  392.         ARGV[dest] = ARGV[src]
  393.         dest++
  394.         src++
  395.     }
  396.     }
  397.     return ArgsLeft
  398. }
  399.  
  400. # Assignment to values in Options[] occurs only in this function.
  401. # Option: Option specifier character.
  402. # Value: Value to be assigned to option, if it takes a value.
  403. # Options[]: Options array to return values in.
  404. # ArgType: Argument type specifier character.
  405. # GotValue: Whether any value is available to be assigned to this option.
  406. # Name: Name of option being processed.
  407. # OptionNum: Number of this option (starting with 1) if set in argv[],
  408. #     or 0 if it was given in a config file or in the environment.
  409. # SingleOpt: true if the value (if any) that is available for this option was
  410. #     given as part of the same command line arg as the option.  Used only for
  411. #     options from the command line.
  412. # specGiven is the option specifier character use, if any (e.g. - or +),
  413. # for use in error messages.
  414. # Global variables: OptErr
  415. # Return value: negative value on error, 0 if option did not require an
  416. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  417. # the arg.
  418. # Current error values:
  419. # -1: Option that required an argument did not get it.
  420. # -2: Value of incorrect type supplied for option.
  421. # -3: Bad type given for option &
  422. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  423. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  424.     # If option takes a value...    [
  425.     NumTypes = "*()#<>]"
  426.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  427.     OptErr = "Bad type given for & option"
  428.     return -3
  429.     }
  430.  
  431.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  432.     if (!GotValue) {
  433.         if (Name != "")
  434.         OptErr = "Variable requires a value -- " Name
  435.         else
  436.         OptErr = "option requires an argument -- " Option
  437.         return -1
  438.     }
  439.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  440.         OptErr = Err
  441.         return -2
  442.     }
  443.     # Mark this as a numeric variable; will be propogated to Options[] val.
  444.     if (ArgType != ":" && ArgType != ";")
  445.         Value += 0
  446.     if ((Instance = ++Options[Option,"count"]) > 1)
  447.         Options[Option,Instance] = Value
  448.     else
  449.         Options[Option] = Value
  450.     }
  451.     # If this is an environ or rcfile assignment & it was given a value...
  452.     else if (!OptionNum && Value != "") {
  453.     UsedValue = 1
  454.     # If the value is "0" or "-" and this is the first instance of it,
  455.     # do not set Options[Option]; this allows an assignment in an rcfile to
  456.     # turn off an option (for the simple "Option in Options" test) in such
  457.     # a way that it cannot be turned on in a later file.
  458.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  459.         Instance = 1
  460.     else
  461.         Instance = ++Options[Option]
  462.     # Save the value even though this is a flag
  463.     Options[Option,Instance] = Value
  464.     }
  465.     # If this is a command line flag and has a - following it in the same arg,
  466.     # it is being turned off.
  467.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  468.     UsedValue = 2
  469.     if (Option in Options)
  470.         Instance = ++Options[Option]
  471.     else
  472.         Instance = 1
  473.     Options[Option,Instance]
  474.     }
  475.     # If this is a flag assignment without a value, increment the count for the
  476.     # flag unless it was turned off.  The indicator for a flag being turned off
  477.     # is that the flag index has not been set in Options[] but it has an
  478.     # instance count.
  479.     else if (Option in Options || !((Option,1) in Options))
  480.     # Increment number of times this flag seen; will inc null value to 1
  481.     Instance = ++Options[Option]
  482.     Options[Option,"num",Instance] = OptionNum
  483.     return UsedValue
  484. }
  485.  
  486. # Option is the option letter
  487. # Value is the value being assigned
  488. # Name is the var name of the option, if any
  489. # ArgType is one of:
  490. # :    String argument
  491. # ;    Non-null string argument
  492. # *    Floating point argument
  493. # (    Non-negative floating point argument
  494. # )    Positive floating point argument
  495. # #    Integer argument
  496. # <    Non-negative integer argument
  497. # >    Positive integer argument
  498. # specGiven is the option specifier character use, if any (e.g. - or +),
  499. # for use in error messages.
  500. # Returns null on success, err string on error
  501. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  502.     if (ArgType == ":")
  503.     return ""
  504.     if (ArgType == ";") {
  505.     if (Value == "")
  506.         Err = "must be a non-empty string"
  507.     }
  508.     # A number begins with optional + or -, and is followed by a string of
  509.     # digits or a decimal with digits before it, after it, or both
  510.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  511.     Err = "must be a number"
  512.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  513.     Err = "may not include a fraction"
  514.     else if (ArgType ~ "[()<>]" && Value < 0)
  515.     Err = "may not be negative"
  516.     # (
  517.     else if (ArgType ~ "[)>]" && Value == 0)
  518.     Err = "must be a positive number"
  519.     if (Err != "") {
  520.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  521.     if (Name != "")
  522.         return ErrStr "variable " substr(Name,1,1) " " Err
  523.     else {
  524.         if (Option == "&")
  525.         Option = Value
  526.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  527.     }
  528.     }
  529.     else
  530.     return ""
  531. }
  532.  
  533. # Note: only the above functions are needed by ProcArgs.
  534. # The rest of these functions call ProcArgs() and also do other
  535. # option-processing stuff.
  536.  
  537. # Opts: Process command line arguments.
  538. # Opts processes command line arguments using ProcArgs()
  539. # and checks for errors.  If an error occurs, a message is printed
  540. # and the program is exited.
  541. #
  542. # Input variables:
  543. # Name is the name of the program, for error messages.
  544. # Usage is a usage message, for error messages.
  545. # OptList the option description string, as used by ProcArgs().
  546. # MinArgs is the minimum number of non-option arguments that this
  547. # program should have, non including ARGV[0] and +h.
  548. # If the program does not require any non-option arguments,
  549. # MinArgs should be omitted or given as 0.
  550. # rcFiles, if given, is a colon-seprated list of filenames to read for
  551. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  552. # by the value of the environment variable HOME.  If a filename begins with
  553. # $, the part from the character after the $ up until (but not including)
  554. # the first character not in [a-zA-Z0-9_] will be searched for in the
  555. # environment; if found its value will be substituted, if not the filename will
  556. # be discarded.
  557. # rcfiles are read in the order given.
  558. # Values given in them will not override values given on the command line,
  559. # and values given in later files will not override those set in earlier
  560. # files, because AssignVal() will store each with a different instance index.
  561. # The first instance of each variable, either on the command line or in an
  562. # rcfile, will be stored with no instance index, and this is the value
  563. # normally used by programs that call this function.
  564. # VarNames is a comma-separated list of variable names to map to options,
  565. # in the same order as the options are given in OptList.
  566. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  567. # searched for in the environment.  If set to -1, all values will be searched
  568. # for in the environment.  Values given in the environment will override
  569. # those given in the rcfiles but not those given on the command line.
  570. # NoRCopt, if given, is an additional letter option that if given on the
  571. # command line prevents the rcfiles from being read.
  572. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  573. # ExclusiveOptions() for a description of exOpts.
  574. # Special options:
  575. # If x is made an option and is given, some debugging info is output.
  576. # h is assumed to be the help option.
  577.  
  578. # Global variables:
  579. # The command line arguments are taken from ARGV[].
  580. # The arguments that are option specifiers and values are removed from
  581. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  582. # The number of elements in ARGV[] should be in ARGC.
  583. # After processing, ARGC is set to the number of elements left in ARGV[].
  584. # The option values are put in Options[].
  585. # On error, Err is set to a positive integer value so it can be checked for in
  586. # an END block.
  587. # Return value: The number of elements left in ARGV is returned.
  588. # Must keep OptErr global since it may be set by InitOpts().
  589. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  590. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  591.     if (MinArgs == "")
  592.     MinArgs = 0
  593.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  594.     optChars)
  595.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  596.     if (ArgsLeft >= 0) {
  597.         OptErr = "Not enough arguments"
  598.         Err = 4
  599.     }
  600.     else
  601.         Err = -ArgsLeft
  602.     printf "%s: %s.\nUse -h for help.\n%s\n",
  603.     Name,OptErr,Usage > "/dev/stderr"
  604.     exit 1
  605.     }
  606.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  607.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  608.     {
  609.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  610.     Err = -e
  611.     exit 1
  612.     }
  613.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  614.     {
  615.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  616.     Err = 1
  617.     exit 1
  618.     }
  619.     return ArgsLeft
  620. }
  621.  
  622. # ReadConfFile(): Read a file containing var/value assignments, in the form
  623. # <variable-name><assignment-char><value>.
  624. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  625. # line and whitespace between the variable name and the assignment character) 
  626. # is stripped.  Lines that do not contain an assignment operator or which
  627. # contain a null variable name are ignored, other than possibly being noted in
  628. # the return value.  If more than one assignment is made to a variable, the
  629. # first assignment is used.
  630. # Input variables:
  631. # File is the file to read.
  632. # Comment is the line-comment character.  If it is found as the first non-
  633. #     whitespace character on a line, the line is ignored.
  634. # Assign is the assignment string.  The first instance of Assign on a line
  635. #     separates the variable name from its value.
  636. # If StripWhite is true, whitespace around the value (whitespace between the
  637. #     assignment char and trailing whitespace on the line) is stripped.
  638. # VarPat is a pattern that variable names must match.  
  639. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  640. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  641. #     a line; no assignment operator is needed.  These variables are set in
  642. #     the output array with a null value.  Lines containing nothing but
  643. #     whitespace are still ignored.
  644. # Output variables:
  645. # Values[] contains the assignments, with the indexes being the variable names
  646. #     and the values being the assigned values.
  647. # Lines[] contains the line number that each variable occured on.  A flag set
  648. #     is record by giving it an index in Lines[] but not in Values[].
  649. # Return value:
  650. # If any errors occur, a string consisting of descriptions of the errors
  651. # separated by newlines is returned.  In no case will the string start with a
  652. # numeric value.  If no errors occur,  the number of lines read is returned.
  653. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  654. FlagsOK,
  655. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  656.     if (Comment != "")
  657.     Comment = "^" Comment
  658.     AssignLen = length(Assign)
  659.     if (VarPat == "")
  660.     VarPat = "."    # null varname not allowed
  661.     while ((Status = (getline Line < File)) == 1) {
  662.     LineNum++
  663.     sub("^[ \t]+","",Line)
  664.     if (Line == "")        # blank line
  665.         continue
  666.     if (Comment != "" && Line ~ Comment)
  667.         continue
  668.     if (Pos = index(Line,Assign)) {
  669.         Var = substr(Line,1,Pos-1)
  670.         Val = substr(Line,Pos+AssignLen)
  671.         if (StripWhite) {
  672.         sub("^[ \t]+","",Val)
  673.         sub("[ \t]+$","",Val)
  674.         }
  675.     }
  676.     else {
  677.         Var = Line    # If no value, var is entire line
  678.         Val = ""
  679.     }
  680.     if (!FlagsOK && Val == "") {
  681.         Errs = Errs \
  682.         sprintf("\nBad assignment on line %d of file %s: %s",
  683.         LineNum,File,Line)
  684.         continue
  685.     }
  686.     sub("[ \t]+$","",Var)
  687.     if (Var !~ VarPat) {
  688.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  689.         LineNum,File,Var)
  690.         continue
  691.     }
  692.     if (!(Var in Lines)) {
  693.         Lines[Var] = LineNum
  694.         if (Pos)
  695.         Values[Var] = Val
  696.     }
  697.     }
  698.     if (Status)
  699.     Errs = Errs "\nCould not read file " File
  700.     close(File)
  701.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  702. }
  703.  
  704. # Variables:
  705. # Data is stored in Options[].
  706. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  707. # Global vars:
  708. # Sets OptErr.  Uses ENVIRON[].
  709. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  710. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  711. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  712. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  713.     split("",filesRead,"")    # make awk know this is an array
  714.     NumVars = split(VarNames,Vars,",")
  715.     TypesInd = Ret = 0
  716.     if (EnvSearch == -1)
  717.     EnvSearch = NumVars
  718.     for (i = 1; i <= NumVars; i++) {
  719.     Var = Vars[i]
  720.     CharOpt = substr(OptList,++TypesInd,1)
  721.     if (CharOpt ~ "^[:;*()#<>&]$")
  722.         CharOpt = substr(OptList,++TypesInd,1)
  723.     Map[Var] = CharOpt
  724.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  725.     # Do not overwrite entries from environment
  726.     if (i <= EnvSearch && Var in ENVIRON &&
  727.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  728.         return Err
  729.     }
  730.  
  731.     numrcFiles = split(rcFiles,fNames,":")
  732.     for (i = 1; i <= numrcFiles; i++) {
  733.     rcFile = fNames[i]
  734.     if (rcFile ~ "^~/")
  735.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  736.     else if (rcFile ~ /^\$/) {
  737.         rcFile = substr(rcFile,2)
  738.         match(rcFile,"^[a-zA-Z0-9_]*")
  739.         envvar = substr(rcFile,1,RLENGTH)
  740.         if (envvar in ENVIRON)
  741.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  742.         else
  743.         continue
  744.     }
  745.     if (rcFile in filesRead)
  746.         continue
  747.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  748.     # may be the same
  749.     filesRead[rcFile]
  750.     if ("x" in Options)
  751.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  752.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  753.     if (retStr > 0)
  754.         READ_RCFILE = 1
  755.     else if (ret != "") {
  756.         OptErr = retStr
  757.         Ret = -1
  758.     }
  759.     for (Var in Lines)
  760.         if (Var in Map) {
  761.         if ((Err = AssignVal(Map[Var],
  762.         Var in Values ? Values[Var] : "",Options,Types[Var],
  763.         Var in Values,Var,0)) < 0)
  764.             return Err
  765.         }
  766.         else {
  767.         OptErr = sprintf(\
  768.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  769.         Lines[Var],rcFile)
  770.         Ret = -1
  771.         }
  772.     }
  773.  
  774.     if ("x" in Options)
  775.     for (Var in Map)
  776.         if (Map[Var] in Options)
  777.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  778.         "/dev/stderr"
  779.         else
  780.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  781.     return Ret
  782. }
  783.  
  784. # OptSets is a semicolon-separated list of sets of option sets.
  785. # Within a list of option sets, the option sets are separated by commas.  For
  786. # each set of sets, if any option in one of the sets is in Options[] AND any
  787. # option in one of the other sets is in Options[], an error string is returned.
  788. # If no conflicts are found, nothing is returned.
  789. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  790. # the exclusions presented by the first set of sets (ab,def,g) if:
  791. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  792. # (a or b is in Options[]) AND (g is in Options) OR
  793. # (d, e, or f is in Options[]) AND (g is in Options)
  794. # An error will be returned due to the exclusions presented by the second set
  795. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  796. # todo: make options given on command line unset options given in config file
  797. # todo: that they conflict with.
  798. function ExclusiveOptions(OptSets,Options,
  799. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  800. SetNum,OSetNum) {
  801.     NumSetSets = split(OptSets,SetSets,";")
  802.     # For each set of sets...
  803.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  804.     # NumSets is the number of sets in this set of sets.
  805.     NumSets = split(SetSets[SetSet],Sets,",")
  806.     # For each set in a set of sets except the last...
  807.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  808.         s1 = Sets[SetNum]
  809.         L1 = length(s1)
  810.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  811.         # If any of the options in this set was given, check whether
  812.         # any of the options in the other sets was given.  Only check
  813.         # later sets since earlier sets will have already been checked
  814.         # against this set.
  815.         if ((c1 = substr(s1,Pos1,1)) in Options)
  816.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  817.             s2 = Sets[OSetNum]
  818.             L2 = length(s2)
  819.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  820.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  821.                 ErrStr = ErrStr "\n"\
  822.                 sprintf("Cannot give both %s and %s options.",
  823.                 c1,c2)
  824.             }
  825.     }
  826.     }
  827.     if (ErrStr != "")
  828.     return substr(ErrStr,2)
  829.     return ""
  830. }
  831.  
  832. # The value of each instance of option Opt that occurs in Options[] is made an
  833. # index of Set[].
  834. # The return value is the number of instances of Opt in Options.
  835. function Opt2Set(Options,Opt,Set,  count) {
  836.     if (!(Opt in Options))
  837.     return 0
  838.     Set[Options[Opt]]
  839.     count = Options[Opt,"count"]
  840.     for (; count > 1; count--)
  841.     Set[Options[Opt,count]]
  842.     return count
  843. }
  844.  
  845. # The value of each instance of option Opt that occurs in Options[] that
  846. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  847. # Other values are made indexes of Set[].
  848. # The return value is the number of instances of Opt in Options.
  849. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  850.     ret = Opt2Set(Options,Opt,aSet)
  851.     for (value in aSet)
  852.     if (substr(value,1,1) == "!")
  853.         nSet[substr(value,2)]
  854.     else
  855.         Set[value]
  856.     return ret
  857. }
  858.  
  859. # Returns true if option Opt was given on the command line.
  860. function CmdLineOpt(Options,Opt,  i) {
  861.     for (i = 1; (Opt,"num",i) in Options; i++)
  862.     if (Options[Opt,"num",i] != 0)
  863.         return 1
  864.     return 0
  865. }
  866. ### End of ProcArgs library
  867. ### Begin head-tail routines
  868.  
  869. # @(#) HeadTail.awk 96/05/09
  870. # 95/04/28 Added tail routines.
  871. # 96/05/09 Added all args to HeadTailInit()
  872.  
  873. # Turn on screen-bounded printing.
  874. # Current implementation sets global vars LINES, COLUMNS, LINEGAP, and COLGAP.
  875. # Sets the number of screen lines and rows to Lines and Rows.
  876. # If -1 is passed for either, turns off bounding in that dimension.
  877. # If either is not set or 0 is passed for it, its value is taken from the
  878. # environment, or if not set there, from terminfo, or if not set there, from
  879. # the defaults (24 and 80).
  880. # By default, the other functions in this library leave a "grace space" of
  881. # 1 column and 1 line.  If LineGap or ColGap is passed and is a non-negative
  882. # value, the line gap is set to it.
  883. function HeadTailInit(Lines,Cols,LineGap,ColGap,  Cmd) {
  884.     # tput will use values in environment, but we want to avoid running
  885.     # it if possible.
  886.     if (Cols > 0)
  887.     COLUMNS = Cols
  888.     else if (!Cols)
  889.     if ("COLUMNS" in ENVIRON)
  890.         COLUMNS = ENVIRON["COLUMNS"]
  891.     else {
  892.         Cmd = "exec tput cols"
  893.         Cmd | getline COLUMNS
  894.         close(Cmd)
  895.         if (COLUMNS == "")
  896.         COLUMNS = 80
  897.     }
  898.     if (Lines > 0)
  899.     LINES = Lines
  900.     else if (!Lines)
  901.     if ("LINES" in ENVIRON)
  902.         LINES = ENVIRON["LINES"]
  903.     else {
  904.         Cmd = "exec tput lines"
  905.         Cmd | getline LINES
  906.         close(Cmd)
  907.         if (LINES == "")
  908.         LINES = 24
  909.     }
  910.     LINEGAP = (LineGap != "" && LineGap >= 0) ? LineGap : 1
  911.     COLGAP = (ColGap != "" && ColGap >= 0) ? ColGap : 1
  912. }
  913.  
  914. # Do screen-bound printing.  
  915. # If LINES  is >0, the last LINES-LINEGAP lines are kept in a circular buffer.  
  916. # When TailFlush() is called, they are printed.
  917. # If LINES = 0, all lines are printed immediately.
  918. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  919. # it.
  920. # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
  921. # saves lines in TailLines[] from 1..LINES-LINEGAP
  922. # Embedded newlines split the line into multiple lines; trailing newlines are
  923. # stripped.  Tabs are expanded to spaces.
  924. function TailPrint(Line) {
  925.     if (!LINES)
  926.     print Line
  927.     else {
  928.     if (++TailPtr > (LINES-LINEGAP))
  929.         TailPtr = 1
  930.     TailLines[TailPtr] = Line
  931.     }
  932. }
  933.  
  934. function TailFlush(  NumPrinted,Lines,Line,i,Buffer,PrintLines) {
  935.     if (!LINES)
  936.     return
  937.     NumPrinted = 0
  938.     PrintLines = LINES-LINEGAP
  939.     # Since lines may contain multiple lines, we must create a buffer to be
  940.     # printed by reading line buffer backwards.
  941.     # Stop when we have copied enough lines, or if we wrap around to the end
  942.     # and find that the entire line buffer was not used.
  943.     while (NumPrinted < PrintLines && TailPtr in TailLines) {
  944.     # Split line into individual lines, then process them last to first
  945.     Num = split(TailLines[TailPtr],Lines,"\n")
  946.     for (i = Num; i >= 1; i--) {
  947.         Line = Lines[i]
  948.         if (i == Num && Line == "")    # discard trailing newline
  949.         continue
  950.         # Put this line at the front of the print buffer
  951.         if (COLUMNS)
  952.         Buffer = substr(TabEx(Line),1,COLUMNS - COLGAP) "\n" Buffer
  953.         else
  954.         Buffer = Line "\n" Buffer
  955.         if (++NumPrinted == PrintLines)
  956.         break
  957.     }
  958.     if (!--TailPtr)    # Wrap pointer if neccessary
  959.         TailPtr = PrintLines
  960.     }
  961.     printf "%s",Buffer
  962. }
  963.  
  964. # Do screen-bound printing.  
  965. # If LINES >0, returns 0 when LINES-LINEGAP lines have been printed by
  966. # HeadPrint().  Otherwise returns 1.
  967. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  968. # it.
  969. # Global vars: uses LINES, COLUMNS, LINEGAP, COLGAP; sets/uses LinesPrinted.
  970. # Line should not include newlines.
  971. function HeadPrint(Line) {
  972.     # Check first, in case some calls of this function to not check return
  973.     # value, and in case LINES is 1.
  974.     if (LINES && LinesPrinted >= (LINES-LINEGAP))
  975.     return 0
  976.     if (COLUMNS)
  977.     print substr(Line,1,COLUMNS - COLGAP)
  978.     else
  979.     print Line
  980.     if (LINES && ++LinesPrinted >= (LINES-LINEGAP))
  981.     return 0
  982.     return 1
  983. }
  984.  
  985. function ColPrint(Line) {
  986.     if (COLUMNS)
  987.     print substr(Line,1,COLUMNS - COLGAP)
  988.     else
  989.     print Line
  990.     return 1
  991. }
  992.  
  993. ### End head-tail routines
  994. ### Begin qsort routines
  995.  
  996. # Arr[] is an array of values with arbitrary indices.
  997. # k[] is returned with numeric indices 1..n.
  998. # The values in k[] are the indices of Arr[],
  999. # ordered so that if Arr[] is stepped through
  1000. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1001. # through in order of the values of its elements.
  1002. # The return value is the number of elements in the arrays (n).
  1003. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1004.     ElNum = 0
  1005.     for (ArrInd in Arr)
  1006.     k[++ElNum] = ArrInd
  1007.     qsortSegment(Arr,k,1,ElNum)
  1008.     return ElNum
  1009. }
  1010.  
  1011. # Sort a segment of an array.
  1012. # Arr[] contains data with arbitrary indices.
  1013. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1014. # This function sorts the elements of arr that are pointed to by
  1015. # k[start..end], swapping the values of elements of k[] so that
  1016. # when this function returns arr[k[start..end]] will be in order.
  1017. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1018.     # handle two-element case explicitly for a tiny speedup
  1019.     if ((end - start) == 1) {
  1020.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1021.         k[start] = tmpe
  1022.         k[end] = tmps
  1023.     }
  1024.     return
  1025.     }
  1026.     # Make sure comparisons act on these as numbers
  1027.     left = start+0
  1028.     right = end+0
  1029.     sepval = Arr[k[int((left + right) / 2)]]
  1030.     # Make every element <= sepval be to the left of every element > sepval
  1031.     while (left < right) {
  1032.     while (Arr[k[left]] < sepval)
  1033.         left++
  1034.     while (Arr[k[right]] > sepval)
  1035.         right--
  1036.     if (left < right) {
  1037.         tmp = k[left]
  1038.         k[left++] = k[right]
  1039.         k[right--] = tmp
  1040.     }
  1041.     }
  1042.     if (left == right)
  1043.     if (Arr[k[left]] < sepval)
  1044.         left++
  1045.     else
  1046.         right--
  1047.     if (start < right)
  1048.     qsortSegment(Arr,k,start,right)
  1049.     if (left < end)
  1050.     qsortSegment(Arr,k,left,end)
  1051. }
  1052.  
  1053. # Arr[] is an array of values with arbitrary indices.
  1054. # k[] is returned with numeric indices 1..n.
  1055. # The values in k are the indices of Arr[],
  1056. # ordered so that if Arr[] is stepped through
  1057. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1058. # through in order of the values of its indices.
  1059. # The return value is the number of elements in the arrays (n).
  1060. # If the indexes are numeric, Numeric should be true, so that they can be
  1061. # compared as such rather than as strings.  Numeric indexes do not have to be
  1062. # contiguous.
  1063. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1064.     ElNum = 0
  1065.     if (Numeric)
  1066.     # Indexes do not preserve numeric type, so must be forced
  1067.     for (ArrInd in Arr)
  1068.         k[++ElNum] = ArrInd+0
  1069.     else
  1070.     for (ArrInd in Arr)
  1071.         k[++ElNum] = ArrInd
  1072.     qsortNumIndByValue(k,1,ElNum)
  1073.     return ElNum
  1074. }
  1075.  
  1076. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1077. # by value.
  1078. # start and end are the starting and ending indexes of the range to be sorted.
  1079. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1080.     # handle two-element case explicitly for a tiny speedup
  1081.     if ((start - end) == 1) {
  1082.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1083.         Arr[start] = tmpe
  1084.         Arr[end] = tmps
  1085.     }
  1086.     return
  1087.     }
  1088.     left = start+0
  1089.     right = end+0
  1090.     sepval = Arr[int((left + right) / 2)]
  1091.     while (left < right) {
  1092.     while (Arr[left] < sepval)
  1093.         left++
  1094.     while (Arr[right] > sepval)
  1095.         right--
  1096.     if (left <= right) {
  1097.         tmp = Arr[left]
  1098.         Arr[left++] = Arr[right]
  1099.         Arr[right--] = tmp
  1100.     }
  1101.     }
  1102.     if (start < right)
  1103.     qsortNumIndByValue(Arr,start,right)
  1104.     if (left < end)
  1105.     qsortNumIndByValue(Arr,left,end)
  1106. }
  1107.  
  1108. ### End qsort routines
  1109.  
  1110. # Put a list of login shells (from /etc/shells) into set LoginShells[].
  1111. # Returns -1 if /etc/shells could not be read, else the number of shells found.
  1112. function ReadShells(LoginShells,  ret,Num,Line) {
  1113.     while (ret = ((getline Line < "/etc/shells") == 1))
  1114.     if (Line ~ "^/") {
  1115.         Num++
  1116.         sub(/[ \t]+/,"",Line)
  1117.         LoginShells[Line]
  1118.     }
  1119.     close("/etc/shells")
  1120.     _DidReadShells = 1
  1121.     return ret ? -1 : Num
  1122. }
  1123.  
  1124. # Makes array shellUser[], indexed by user name, tell whether that each user
  1125. # has a shell in /etc/shells.
  1126. # Returns 1 on success, 0 if there is a problem reading /etc/shells or
  1127. # /etc/passwd.
  1128. function makeShellUser(shellUser,  LoginShells,ret,oFS) {
  1129.     if (ReadShells(LoginShells) < 0)
  1130.     return 0
  1131.     oFS = FS
  1132.     FS = ":"
  1133.     while (ret = ((getline < "/etc/passwd") == 1))
  1134.     shellUser[$1] = ($7 in LoginShells)
  1135.     close("/etc/passwd")
  1136.     FS = oFS
  1137.     return !ret
  1138. }
  1139. ### Begin ps lib
  1140. # getPS 1.1    
  1141. # 96/10/09    john h. dubois iii (john@armory.com)
  1142. # 96/02/11    Added Debug flag.
  1143. # 96/05/09    Added COMM field.
  1144. # 96/05/23    Added selection args, and saving of "ps" PID.
  1145. # 96/05/25    Added makePSline()
  1146. # 96/10/09    Added RUSER field.
  1147. # 96/12/14    Added CMDT field.
  1148.  
  1149. # Note: makePSline() needs assign() from array lib.
  1150. # to do: generalize based on -o args to 5.0 ps
  1151.  
  1152. # Do a ps -f and save the output into an array, indexed by pid and field name.
  1153. # Input vars:
  1154. # Fields: Comma-separated list of fields to put in Procs.
  1155. # If Debug is true, debugging info is output.
  1156. # selectionArgs may be set to ps options that will report on selected processes
  1157. # (e.g. -usomeone -ttty01)
  1158. # The default for selectionArgs is -e, which causes information on all
  1159. # processes to be recorded.
  1160. #
  1161. # Output vars:
  1162. # PIDs[]: the set of all PIDs seen.
  1163. # Also, the element with index "ps" is set to the PID for the ps process.
  1164. # Procs[pid,fieldname]: output by field.
  1165. #
  1166. # Possible fields are:
  1167. # UID: User ID; name if available, else number.
  1168. # RUSER: Real user ID; name if available, else number.  Only available under
  1169. #       5.0, and cannot be requested along with UID.
  1170. # PPID: Parent process ID.
  1171. # C: CPU scheduling.
  1172. # STIME: Start time.  If the start time in the ps output contains a space,
  1173. # it is replaced with a "-".  "-" is returned for a defunct process.
  1174. # TTY: tty name; may or may not have leading "tty" part.  "-" for defunct proc;
  1175. # "?" for proc with no controlling tty.
  1176. # TIME: CPU time used.
  1177. # CMD: First element of arg vector.
  1178. # CMDT: Like CMD, but just the tail (leading path components removed), unless
  1179. #       the path ends with /, in which case it is identical to CMD.
  1180. # ARGS: Entire (truncated) arg vector (command + args).
  1181. # LINE: Entire ps output line.
  1182. # COMM: Process accounting name of process: the name of the executable file,
  1183. #       without path.  This is only available under 5.0, and cannot be
  1184. #       requested along with CMD/CMDT or ARGS.
  1185. #
  1186. # The header line read is also put in Procs with the index "Header".
  1187. # The PIDs of the children of each process are put in a comma-separated list
  1188. # in Children[pid].
  1189. # Return value: the number of processes found, or -2 if an invalid field name
  1190. # is passed, or -1 if an error occurs reading from ps.
  1191. # Globals: FS is set to " "
  1192. #
  1193. # ps -f produces output in these forms, under various conditions & releases:
  1194. #     UID   PID  PPID  C    STIME     TTY        TIME CMD
  1195. #    root 10118 10107  2   Jan-03   ttyp0    00:00:05 -ksh
  1196. #    root 10118 10107  2   Jan 03   ttyp0    00:00:05 -ksh
  1197. #    root 18197     1  0 08:02:56   ttyp0    00:00:03 /usr/bin/X11/scoterm -geo
  1198. function getPS(PIDs,Procs,Fields,Children,Debug,selectionArgs,
  1199. stimeI,pidI,ttyI,ppidI,WantLine,psArgs,psSet,newPS,
  1200. FieldNames,Wanted,Cmd,getI,Field2Ind,i,Name,Lines,WantArgs,Header,CmdIndex,fn,
  1201. wantCmdt) {
  1202.     FS = " "    # magic pattern to reset FS to its default special behaviour
  1203.     split("UID,PID,PPID,C,STIME,TTY,TIME,CMD",FieldNames,",")
  1204.     # psSet[] maps field number to field name
  1205.     split("user,pid,ppid,c,stime,tty,time,args",psSet,",")
  1206.     # Alt[] maps new ps field names to field numbers
  1207.     Alt["RUSER"] = 1
  1208.     Alt["COMM"] = 8
  1209.     FieldNames[0] = "LINE"
  1210.     for (i in FieldNames)    # Field2Ind[] maps field names to field numbers
  1211.     Field2Ind[FieldNames[i]] = i
  1212.     split(Fields,Wanted,",")
  1213.     pidI = Field2Ind["PID"]
  1214.     ppidI = Field2Ind["PPID"]
  1215.     stimeI = Field2Ind["STIME"]
  1216.     ttyI = Field2Ind["TTY"]
  1217.     timeI = Field2Ind["TIME"]
  1218.     cmdI = Field2Ind["CMD"]
  1219.     psArgs = "-f"
  1220.     for (i in Wanted) {
  1221.     Name = Wanted[i]
  1222.     if (Debug)
  1223.         printf "Asked for %s\n",Name > "/dev/stderr"
  1224.     # getI[] is made to contain the indices of fields to record
  1225.     if (Name == "ARGS")
  1226.         WantArgs = 1
  1227.     else if (Name == "LINE")
  1228.         WantLine = 1
  1229.     else if (Name == "CMDT")
  1230.         wantCmdt = 1
  1231.     else if (Name in Alt) {        # New ps fields
  1232.         newPS = 1
  1233.         # Change the name of this field to that of the alternate requested
  1234.         psSet[Alt[Name]] = tolower(Name)
  1235.         fn = Field2Ind[Name] = Alt[Name] # Map this name to its field number
  1236.         getI[fn]
  1237.         FieldNames[fn] = Name
  1238.     }
  1239.     else if (Name in Field2Ind)
  1240.         getI[Field2Ind[Name]]
  1241.     else
  1242.         return -2
  1243.     }
  1244.     if (newPS) {
  1245.     psArgs = ""
  1246.     for (i = 1; i in psSet; i++)
  1247.         psArgs = psArgs " -o" psSet[i]
  1248.     }
  1249.     Lines = 0
  1250.     if (selectionArgs == "")
  1251.     selectionArgs = "-e"
  1252.     Cmd = "echo $$; exec /bin/ps " selectionArgs " " psArgs " < /dev/null"
  1253.     if ((Cmd | getline PIDs["ps"]) != 1)
  1254.     return -1
  1255.     if ((Cmd | getline Header) != 1)
  1256.     return -1
  1257.     Procs["Header"] = Header
  1258.     if (!(CmdIndex = index(Header,"CMD")) &&
  1259.     !(CmdIndex = index(Header,"COMMAND")))
  1260.     return -1
  1261.     while ((Cmd | getline) == 1) {
  1262.     PIDs[pid = $pidI]
  1263.     if (Debug)
  1264.         printf "Process %d (%d fields): %s\n",pid,NF,$0 > "/dev/stderr"
  1265.     ppid = $ppidI
  1266.     if (ppid in Children)
  1267.         Children[ppid] = Children[ppid] "," pid
  1268.     else
  1269.         Children[ppid] = pid
  1270.     if (WantArgs)
  1271.         Procs[pid,"ARGS"] = substr($0,CmdIndex)
  1272.     # Handle this as a special case so that it can be set before the
  1273.     # line is (possibly) modified
  1274.     if (WantLine)
  1275.         Procs[pid,"LINE"] = $0
  1276.     # Time field with either contain a : (time), a - (new date format),
  1277.     # or neither, in which case it occupies 2 fields (old date format).
  1278.     if (NF == 6) {    # old ps defunct proc
  1279.         # Assign new values to fields, from right to left to avoid
  1280.         # overwriting fields before value is moved
  1281.         $cmdI = $ttyI
  1282.         $timeI = $stimeI
  1283.         $ttyI = "-"
  1284.         $stimeI = "-"
  1285.     }
  1286.     if ($stimeI !~ "[-:]") {
  1287.         if (!timePos)
  1288.         timePos = index($0,$stimeI)
  1289.         # Replace space in stime field with "-"
  1290.         $0 = substr($0,1,timePos+2) "-" substr($0,timePos+5)
  1291.     }
  1292.     if (wantCmdt) {
  1293.         Procs[pid,"CMDT"] = $cmdI
  1294.         if ($cmdI !~ "/$")
  1295.         sub(".*/","",Procs[pid,"CMDT"])
  1296.     }
  1297.     for (i in getI) {
  1298.         Procs[pid,FieldNames[i]] = $i
  1299.         if (Debug)
  1300.         printf "%s=%s ",FieldNames[i],$i > "/dev/stderr"
  1301.     }
  1302.     if (Debug)
  1303.         print "" > "/dev/stderr"
  1304.     Lines++
  1305.     }
  1306.     close(Cmd)
  1307.     return Lines
  1308. }
  1309.  
  1310. # makePSline: generate a line containing desired fields from ps data.
  1311. # pid is the ID of the process to generate a line for.
  1312. # If a pid of -1 is passed, a header line is returned.
  1313. # Procs[] is the ps data, as generated by getPS().
  1314. # Fields[] is the set of fields desired in the output, with indexes starting
  1315. #    at 1.  The values are field names as e.g. passed to getPS().
  1316. # Sep is the separator to put between fields.  If null, a single space is used.
  1317. # Return value: a line consisting of the fields requested, in the order of
  1318. # their indices in Fields[].
  1319. # Example:
  1320. # split("UID,PID,PPID,C,STIME,TTY,TIME,CMD",FieldNames,",")
  1321. # makePSline(pid,psOut,FieldNames)
  1322. function makePSline(pid,Procs,Fields,Sep,  i,fieldName,line,width,value) {
  1323.     if (Sep == "")
  1324.     Sep = " "
  1325.     if (!("PID" in _makePSlineWidths))
  1326.     # Make TIME before right-adjusted; some versions of ps drop leading
  1327.     # 0 fields from it.
  1328.     Assign(_makePSlineWidths,
  1329.     "UID=-8 PID=5 PPID=5 C=1 STIME=-8 TTY=-4 TIME=8 COMM=-8"," ","=")
  1330.     for (i = 1; i in Fields; i++) {
  1331.     fieldName = Fields[i]
  1332.     if (fieldName in _makePSlineWidths)
  1333.         width = _makePSlineWidths[fieldName]
  1334.     else
  1335.         width = ""
  1336.     if (pid == -1)
  1337.         value = fieldName
  1338.     else if (fieldName == "PID")
  1339.         value = pid
  1340.     else
  1341.         value = Procs[pid,fieldName]
  1342.     if (fieldName == "TTY")
  1343.         sub("^tty","",value)
  1344.     line = line Sep sprintf("%" width "s",value)
  1345.     }
  1346.     return substr(line,length(Sep)+1)
  1347. }
  1348.  
  1349. ### End ps lib
  1350. ### Begin set library
  1351. # 96/05/23 added return values  jhdiii
  1352. # 96/05/25 added set2list()
  1353. # 97/01/26 Added AOnly(), Exclusive()
  1354.  
  1355. # Return value: the number of new elements added to Inter
  1356. function Intersection(A,B,Inter,  Elem,Count) {
  1357.     for (Elem in A)
  1358.     if (Elem in B && !(Elem in Inter)) {
  1359.         Inter[Elem]
  1360.         Count++
  1361.     }
  1362.     return Count
  1363. }
  1364.  
  1365. # Any element that is in A or B but not both and which is not already in
  1366. # Excl is added to Excl.
  1367. # Return value: the number of new elements added to Excl
  1368. function Exclusive(A,B,Excl) {
  1369.     return AOnly(A,B,Excl) + AOnly(B,A,Excl)
  1370. }
  1371.  
  1372. # Any element that is in A and not in B or aOnly is added to aOnly.
  1373. # Return value: the number of new elements added to aOnly.
  1374. function AOnly(A,B,aOnly,  Elem,Count) {
  1375.     for (Elem in A)
  1376.     if (!(Elem in B) && !(Elem in aOnly)) {
  1377.         aOnly[Elem]
  1378.         Count++
  1379.     }
  1380.     return Count
  1381. }
  1382.  
  1383. # Return value: the number of new elements added to Both
  1384. function Union(A,B,Both) {
  1385.     return CopySet(A,Both) + CopySet(B,Both)
  1386. }
  1387.  
  1388. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  1389. # Return value: the number of elements deleted.
  1390. function SubtractSet(Minuend,Subtrahend,  Elem,nDel) {
  1391.     for (Elem in Subtrahend)
  1392.     if (Elem in Minuend) {
  1393.         delete Minuend[Elem]
  1394.         nDel++
  1395.     }
  1396.     return nDel
  1397. }
  1398.  
  1399. # Return value: the number of new elements added to To
  1400. function CopySet(From,To,  Elem,n) {
  1401.     for (Elem in From)
  1402.     if (!(Elem in To)) {
  1403.         To[Elem]
  1404.         n++
  1405.     }
  1406.     return n
  1407. }
  1408.  
  1409. # Returns 1 if Set is empty, 0 if not.
  1410. function IsEmpty(Set,  i) {
  1411.     for (i in Set)
  1412.     return 0
  1413.     return 1
  1414. }
  1415.  
  1416. # MakeSet: make a set from a list.
  1417. # An index with the name of each element of the list is created in the given
  1418. # array.
  1419. # Input variables:
  1420. # Elements is a string containing the list of elements.
  1421. # Sep is the character that separates the elements of the list.
  1422. # Output variables:
  1423. # Set is the array.
  1424. # Return value: the number of new elements added to the set.
  1425. function MakeSet(Set,Elements,Sep,  i,Num,Names,nFound,ind) {
  1426.     nFound = 0
  1427.     Num = split(Elements,Names,Sep)
  1428.     for (i = 1; i <= Num; i++) {
  1429.     ind = Names[i]
  1430.     if (!(ind in Set)) {
  1431.         Set[ind]
  1432.         nFound++
  1433.     }
  1434.     }
  1435.     return nFound
  1436. }
  1437.  
  1438. # Returns the number of elements in set Set
  1439. function NumElem(Set,  elem,Num) {
  1440.     for (elem in Set)
  1441.     Num++
  1442.     return Num
  1443. }
  1444.  
  1445. # Remove all elements from Set
  1446. function DeleteAll(Set,  i) {
  1447.     split("",Set,",")
  1448. }
  1449.  
  1450. # Returns a list of all of the elements in Set[], with each pair of elements
  1451. # separated by Sep.
  1452. function set2list(Set,Sep,  list,elem) {
  1453.     for (elem in Set)
  1454.     list = list Sep elem
  1455.     return substr(list,2)    # skip 1st separator
  1456. }
  1457. ### End set library
  1458.